//=============================================================================
// System  : MatchKit
// File    : MatchContext.cs
// Author  : Fabio Bussu  (matchkit@fabiobussu.it)
// Updated : 04/16/2013
// Note    : Copyright 2013, Fabio Bussu, All rights reserved
//
// This code is published under the Microsoft Public License (Ms-PL). A copy
// of the license should be distributed with the code.  It can also be found
// at the project website: http://matchkit.codeplex.com. This notice, the
// author's name, and all copyright notices must remain intact in all
// applications, documentation, and source files.
//
// Version     Date     Who  Comments
// ============================================================================
// 1.0.0.0  04/16/2013   FB  First draft
//=============================================================================
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Threading;

namespace MatchKit
{
	/// <summary>
	/// Provides the context informations for the matching process.
	/// </summary>
	/// <threadsafety static="true" instance="false" />
	/// <serialization binary="true" contract="true" />
	/// <conceptualLink target="0a06b2a9-5f58-409a-ba62-639cfd77bca7" />
	#if DC
	[DataContract]
	#endif
	#if SZ
	[Serializable]
	#endif
	public class MatchContext
	{
		private string _targetText;
		/// <summary>
		/// Gets the string target of the matches.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public string TargetText
		{
			get { return _targetText; }
			private set { _targetText = value; }
		}

		#if DC
		[DataMember]
		#endif
		private int _len;

		#if DC
		[DataMember]
		#endif
		private char _prev = '\0';
		#if DC
		[DataMember]
		#endif
		private int _i = 0;
		#if DC
		[DataMember]
		#endif
		private int _line;
		#if DC
		[DataMember]
		#endif
		private int _column;
		#if DC
		[DataMember]
		#endif
		private char _current;

		// options
		#if DC
		[DataMember]
		#endif
		private CancellationFlag _cancellationToken;

		/// <summary>
		/// Ticks of the startup matching process
		/// </summary>
		#if DC
		[DataMember]
		#endif
		internal Ref<int> _startedAtTicks = new Ref<int>(0);

		#if DC
		[DataMember]
		#endif
		private Dictionary<string, object> _userData = new Dictionary<string, object>(StringComparer.CurrentCultureIgnoreCase);
		/// <summary>
		/// Gets a dictionary for saving and passing custom data across the matching process.
		/// </summary>
		/// <serialization member="true" />
		public Dictionary<string, object> UserData
		{
			get { return _userData; }
		}

		/// <summary>
		/// Gets the creating options for the current context.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public MatchOptions Options
		{
			get; private set;
		}

		/// <summary>
		/// Timeout in millisecondi
		/// </summary>
		#if DC
		[DataMember]
		#endif
		private int _timeout;

		/// <summary>
		/// Initializes a new context with default options.
		/// </summary>
		/// <param name="s">Source string to be matched. This value cannot be null.</param>
		public MatchContext(string s)
			: this(s, new MatchOptions())
		{
		}

		/// <summary>
		/// Initializes a new context with the specififed options.
		/// </summary>
		/// <param name="s">Source string to be matched. This value cannot be null.</param>
		/// <param name="options">Options to be applied on this context. This value cannot be null.</param>
		public MatchContext(string s, MatchOptions options)
		{
			if (s == null)
				throw new ArgumentNullException("s", "source string cannot be null");
			if (options == null)
				throw new ArgumentNullException("options", "options cannot be null; use the constructor without the options instead");

			_targetText = s;
			_len = _targetText.Length;
			_line = 1;
			if (_len == 0)
			{
				_column = 0;
				_current = '\0';
			}
			else
			{
				_column = 1;
				_current = _targetText[0];
			}
			Options = options;
			_timeout = (int)options.Timeout.TotalMilliseconds;
			// mi salvo alcune singole opzioni
			_cancellationToken = options.CancellationFlag;
			IsTraceEnabled = options.Tracer != null;
			Tracer = options.Tracer;
		}

		/// <summary>
		/// Returns a cloned context instance that can be used to try a match. If the search on the branched context 
		/// will be success, the branched context must be committed on the originating context with <see cref="M:MatchKit.MatchContext.Commit(MatchContext)"/>; 
		/// otherwise the branched context can be discarded.
		/// </summary>
		/// <returns>A branched context instance.</returns>
		public virtual MatchContext Branch()
		{
			return (MatchContext)MemberwiseClone();
		}

		/// <summary>
		/// Confirms a previously branched context on the current context instance. Main informations will be copied 
		/// from the branched context to the current context.
		/// </summary>
		/// <param name="branch">A previously branched context.</param>
		public virtual void Commit(MatchContext branch)
		{
			if (branch == null)
				throw new ArgumentNullException("branch", "branch cannot be null");

			_prev = branch._prev;
			_i = branch._i;
			_line = branch._line;
			_column = branch._column;
			_current = branch._current;
		}

		/// <summary>
		/// Gets the current absolute position (zero-based) inside the <see cref="P:MatchKit.MatchContext.TargetText"/>.
		/// </summary>
		public int Index { get { return _i; } }

		/// <summary>
		/// Gets the current location inside the <see cref="P:MatchKit.MatchContext.TargetText"/>. The location includes the current 
		/// index, current line and current column positions.
		/// </summary>
		public MatchLocation Location { get { return new MatchLocation(_line, _column, _i); } }

		/// <summary>
		/// Gets the current character of the <see cref="P:MatchKit.MatchContext.TargetText"/>.
		/// </summary>
		public char Current { get { return _current; } }

		/// <summary>
		/// Gets a value indicating whether the matching process has been requested to cancel.
		/// </summary>
		public bool IsCancellationRequested
		{
			get { return _cancellationToken.IsCancellationRequested; }
		}

		/// <summary>
		/// Checks whether the current matching process time is greater than the <see cref="P:MatchKit.MatchOptions.Timeout"/> specified 
		/// with the initial <see cref="T:MatchKit.MatchOptions"/>.
		/// </summary>
		public bool IsTimedOut
		{
			get
			{
				// se non e' richiesto nessun check del timeout
				if (_timeout <= 0)
					return false;

				var st = _startedAtTicks.Value;

				// se e' stato impostato il momento corrente
				if (st > 0)
				{
					return (Environment.TickCount - st > _timeout);
				}

				// devo valorizzare il momento dello start

				// se e' richiesto il timeout, prendo il momento corrente
				if (_timeout > 0)
					_startedAtTicks.Value = Environment.TickCount;
				else  // imposto il momento corrente rimane a -1
					_startedAtTicks.Value = -1;

				return false;
			}
		}

		/// <summary>
		/// Advances the current position inside the <see cref="P:MatchKit.MatchContext.TargetText"/>.
		/// </summary>
		/// <returns>The new current character.</returns>
		public virtual char NextCharacter()
		{
			_prev = _current;

			if (_current == '\0')
				return _current;

			// la new-line e' identificata come \n oppure come \r (non seguito da \n)

			if (_current == '\r' && Peek() != '\n')
			{
				_line++;
				_column = 0;
			}
			else if (_current == '\n')
			{
				_line++;
				_column = 0;
			}

			if (_i >= (_len - 1))
			{
				_current = '\0';
				_i++;
				_column++;
				return _current;
			}

			_current = _targetText[++_i];
			_column++;

			return _current;
		}

		/// <summary>
		/// Advances the current position by a specified number of characters.
		/// </summary>
		/// <param name="count">Number of characters to skip.</param>
		/// <returns>The number of character not advanced. Zero if the end of string is not reached.</returns>
		public int NextFor(int count)
		{
			while (count-- > 0)
				if (NextCharacter() == '\0')
					break;
			return count;
		}

		/// <summary>
		/// Performs a comparison between a specified string and the current string available from the current location. 
		/// If the comparison success, the current position is advanced by the length of the compared string.
		/// </summary>
		/// <param name="s">The string to compare.</param>
		/// <param name="ignoreCase">Specifies whether to perform a case insensitive comparison.</param>
		/// <returns>A value indicating whether the specified string is found.</returns>
		public bool NextWith(string s, bool ignoreCase)
		{
			if (s == null)
				throw new ArgumentNullException("s", "string cannot be null");

			bool found = string.Compare(s, 0, _targetText, _i, s.Length
				, ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture) == 0;
			if (found)
				NextFor(s.Length);
			return found;
		}

		/// <summary>
		/// Advances the current position while the current character is not equal to any of the specified charaters 
		/// or the end sof string is reached.
		/// </summary>
		/// <param name="characters">Array of terminating characters.</param>
		/// <returns>The number of characters fetched.</returns>
		public int NextTo(params char[] characters)
		{
			return NextTo(ch => Array.IndexOf(characters, ch) < 0);
		}

		/// <summary>
		/// Advances the current position while the current character is not equal to the specified charater 
		/// or the end sof string is reached.
		/// </summary>
		/// <param name="ch">Terminating character.</param>
		/// <returns>The number of characters fetched.</returns>
		public int NextTo(char ch)
		{
			return NextTo(c => ch != c);
		}

		/// <summary>
		/// Advances the current position while the specified predicate returns true or the end sof string is reached.
		/// </summary>
		/// <param name="predicate">Predicate to evaluate the terminating condition.</param>
		/// <returns>The number of characters fetched.</returns>
		public int NextTo(Func<char, bool> predicate)
		{
			if (predicate == null)
				throw new ArgumentNullException("predicate", "predicate cannot be null");

			int c = 0;
			while (!IsEos && predicate(_current))
			{
				NextCharacter();
				c++;
			}

			return c;
		}

		/// <summary>
		/// Advances the current position while the specified predicate returns true or the end sof string is reached. 
		/// The second parameter of the predicate is the current absolute position.
		/// </summary>
		/// <param name="predicate">Predicate to evaluate the terminating condition.</param>
		/// <returns>The number of characters fetched.</returns>
		public int NextTo(Func<char, int, bool> predicate)
		{
			if (predicate == null)
				throw new ArgumentNullException("predicate", "predicate cannot be null");

			int p = 0, c = 0;
			while (!IsEos && predicate(_current, p++))
			{
				NextCharacter();
				c++;
			}

			return c;
		}

		/// <summary>
		/// Finds a specified string starting from the current position.
		/// </summary>
		/// <param name="s">The string to find.</param>
		/// <param name="ignoreCase">Specifies whether to perform a case insensitive comparison.</param>
		/// <returns>The absolute position of the found string. -1 if not found.</returns>
		public int Find(string s, bool ignoreCase = true)
		{
			if (s == null)
				throw new ArgumentNullException("s", "string cannot be null");
			return _targetText.IndexOf(s, _i
				, ignoreCase ? StringComparison.CurrentCultureIgnoreCase : StringComparison.CurrentCulture);
		}

		/// <summary>
		/// Returns the next character without advance the current position.
		/// </summary>
		/// <returns>The next character.</returns>
		public char Peek()
		{
			return Peek(1);
		}

		/// <summary>
		/// Returns a next character without advance the current position.
		/// </summary>
		/// <param name="forward">Positive offset from the current position where to keep the character.</param>
		/// <returns>The next character.</returns>
		public char Peek(int forward)
		{
			if (forward <= 0)
				throw new ArgumentOutOfRangeException("forward", "Value must be greater than 0");
			if (_i >= (_len - forward))
				return '\0';
			return _targetText[_i + forward];
		}

		/// <summary>
		/// Checks if the next character, without advance the current position, is equals to the specified character.
		/// </summary>
		/// <returns>The character to check.</returns>
		public bool Peek(char ch)
		{
			return Peek() == ch;
		}

		/// <summary>
		/// Returns the string by a specified length starting from the current position. The current character is not included.
		/// </summary>
		/// <param name="len">Length of the string to peek.</param>
		/// <returns>The peeked string.</returns>
		public string PeekString(int len)
		{
			return PeekString(len, false);
		}

		/// <summary>
		/// Returns the string by a specified length starting from the current position. The current character is not included.
		/// </summary>
		/// <param name="len">Length of the string to peek.</param>
		/// <param name="includeCurrent">Specifies if to keep the current character.</param>
		/// <returns>The peeked string.</returns>
		public string PeekString(int len, bool includeCurrent)
		{
			if ((_i + len - (includeCurrent ? 1 : 0)) >= _len)
				return null;
			return _targetText.Substring(_i + (includeCurrent ? 0 : 1), len);
		}

		/// <inheritdoc />
		public override string ToString()
		{
			return _targetText;
		}

		/// <summary>
		/// Gets the text from the current position to the end of the string.
		/// </summary>
		public string RemainingContent { get { return _targetText.Substring(_i, _len - _i); } }

		/// <summary>
		/// Gets if a <see cref="P:MatchKit.MatchContext.Tracer"/> has been specified.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public bool IsTraceEnabled { get; private set; }

		/// <summary>
		/// Gets the tracer specified with the initial options.
		/// </summary>
		/// <serialization member="true" />
		#if DC
		[DataMember]
		#endif
		public IMatchTracer Tracer { get; private set; }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>end</b> of the target text.
		/// </summary>
		public bool IsEos { get { return _current == '\0'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is equals to <paramref name="ch"/> parameter.
		/// </summary>
		public bool Is(char ch) { return _current == ch; }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is a <b>letter</b>.
		/// </summary>
		public bool IsLetter { get { return char.IsLetter(_current); } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is a <b>digit</b>.
		/// </summary>
		public bool IsDigit { get { return char.IsDigit(_current); } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is a <b>letter or digit</b>.
		/// </summary>
		public bool IsLetterOrDigit { get { return char.IsLetterOrDigit(_current); } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is a <b>whitespace</b>, this includes newline characters.
		/// </summary>
		public bool IsWhiteSpace { get { return char.IsWhiteSpace(_current); } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>colon</b> symbol (<b>:</b>).
		/// </summary>
		public bool IsColon { get { return _current == ':'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>tab</b> symbol (<b>\t</b>).
		/// </summary>
		public bool IsTab { get { return _current == '\t'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>number</b> symbol (<b>#</b>).
		/// </summary>
		public bool IsNumber { get { return _current == '#'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>comma</b> symbol (<b>,</b>).
		/// </summary>
		public bool IsComma { get { return _current == ','; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>space</b> symbol (<b> </b>).
		/// </summary>
		public bool IsSpace { get { return _current == ' '; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>plus</b> symbol (<b>+</b>).
		/// </summary>
		public bool IsPlus { get { return _current == '+'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>minus</b> symbol (<b>-</b>).
		/// </summary>
		public bool IsMinus { get { return _current == '-'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>dot</b> symbol (<b>.</b>).
		/// </summary>
		public bool IsDot { get { return _current == '.'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>ampersand</b> symbol (<b>&amp;</b>).
		/// </summary>
		public bool IsAmpersand { get { return _current == '&'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>single quote</b> symbol (<b>'</b>).
		/// </summary>
		public bool IsSingleQuote { get { return _current == '\''; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>asterisk</b> symbol (<b>*</b>).
		/// </summary>
		public bool IsAsterisk { get { return _current == '*'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>at</b> symbol (<b>@</b>).
		/// </summary>
		public bool IsAt { get { return _current == '@'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>backquote</b> symbol (<b>`</b>).
		/// </summary>
		public bool IsBackQuote { get { return _current == '`'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>backslash</b> symbol (<b>\</b>).
		/// </summary>
		public bool IsBackslash { get { return _current == '\\'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>open brace</b> symbol (<b>{</b>).
		/// </summary>
		public bool IsOpenBrace { get { return _current == '{'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>close brace</b> symbol (<b>}</b>).
		/// </summary>
		public bool IsCloseBrace { get { return _current == '}'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>open bracket</b> symbol (<b>[</b>).
		/// </summary>
		public bool IsOpenBracket { get { return _current == '['; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>close bracket</b> symbol (<b>]</b>).
		/// </summary>
		public bool IsCloseBracket { get { return _current == ']'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>carat</b> symbol (<b>^</b>).
		/// </summary>
		public bool IsCarat { get { return _current == '^'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>open paranthesis</b> symbol (<b>(</b>).
		/// </summary>
		public bool IsOpenParenthesis { get { return _current == '('; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>close parenthesis</b> symbol (<b>)</b>).
		/// </summary>
		public bool IsCloseParenthesis { get { return _current == ')'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>dollar</b> symbol (<b>$</b>).
		/// </summary>
		public bool IsDollar { get { return _current == '$'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>equal</b> symbol (<b>=</b>).
		/// </summary>
		public bool IsEqual { get { return _current == '='; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>exclamation</b> symbol (<b>!</b>).
		/// </summary>
		public bool IsExclamation { get { return _current == '!'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>greater</b> symbol (<b>&gt;</b>).
		/// </summary>
		public bool IsGreater { get { return _current == '>'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>less</b> symbol (<b>&lt;</b>).
		/// </summary>
		public bool IsLess { get { return _current == '<'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>percent</b> symbol (<b>%</b>).
		/// </summary>
		public bool IsPercent { get { return _current == '%'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>pipe</b> symbol (<b>|</b>).
		/// </summary>
		public bool IsPipe { get { return _current == '|'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>quote</b> symbol (<b>"</b>).
		/// </summary>
		public bool IsQuote { get { return _current == '\"'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>slash</b> symbol (<b>/</b>).
		/// </summary>
		public bool IsSlash { get { return _current == '/'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>tilde</b> symbol (<b>~</b>).
		/// </summary>
		public bool IsTilde { get { return _current == '~'; } }

		/// <summary>
		/// Gets a value indicating whether the <see cref="P:MatchKit.MatchContext.Current"/> character is the <b>underscore</b> symbol (<b>_</b>).
		/// </summary>
		public bool IsUnderscore { get { return _current == '_'; } }
	}
}
